package textchorus.generator;

import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.Vector;

import rita.RiAnalyze;
import textchorus.PSketch;
import textchorus.sparql.SparqlQuery;
import ddf.minim.AudioOutput;
import ddf.minim.Minim;
import ddf.minim.signals.SineWave;

public class ChordMaker implements Runnable {
	private volatile Thread myThread;

	@SuppressWarnings("unused")
	private final static int[] maleFreqRange = { 85, 155 };
	@SuppressWarnings("unused")
	private final static int[] femaleFreqRange = { 165, 255 };
	private final static String[] TTS_PHONEMES = { "AA", "AE", "AH", "AO",
			"AW", "AX", "AXR", "AY", "b", "CH", "d", "DH", "DX", "EH", "EL",
			"EM", "EN", "ER", "EY", "f", "g", "HH", "HV", "IH", "IY", "JH",
			"k", "l", "m", "n", "NX", "NG", "OW", "OY", "p", "r", "s", "SH",
			"t", "TH", "UH", "UW", "v", "w", "y", "z", "ZH", "PAU", "H#",
			"BRTH" };

	public String currentWord;

	private PSketch p;
	private String inputText;
	private String sqStr = "";

	private boolean debug = false;
	private boolean isBusy = false;

	// a map of the words contains an ArrayList of their phonemes
	private Map<String, Vector<String>> words = new LinkedHashMap<String, Vector<String>>();

	private int speed; // The number of milliseconds to play back per letter in
	// the word.
	private int midiNoteOffset;
	private int mode;
	private TreeMap<String, Float> phonemeFreq = new TreeMap<String, Float>();
	private TreeMap<String, SineWave> phonemeSine = new TreeMap<String, SineWave>();

	private RiAnalyze analyze;
	private AudioOutput out;
	private int pause;

	// CONSTRUCTOR
	public ChordMaker(PSketch p) {
		this.p = p;
		analyze = new RiAnalyze(null);
		Minim.start(null);
		out = Minim.getLineOut(Minim.STEREO, 512);

		myThread = new Thread(this);
		myThread.start();
	}

	public void setSpeed(int s) {
		speed = s;
	}

	public void setMidiNoteOffset(int mo) {
		midiNoteOffset = mo;
	}

	public void setText(String text) {
		inputText = text;
	}

	public void loadFreq() {
		// get freq step size from by range/# phonemes
		// int freqStep = (femailFreqRange[1] - maleFreqRange[0]) /
		// TTS_PHONEMES.length;

		// populate the Map with the Phonemes as keys
		// and their frequency as value
		// this may not be necessary
		for (int i = 0; i < TTS_PHONEMES.length; i++) {
			phonemeFreq.put(TTS_PHONEMES[i], new Float(midiFreq(i
					+ midiNoteOffset)));
			// phonemeFreq.put(TTS_PHONEMES[i], new Integer(freqStep * i));
		}

		// populate the Map with the phonemes as keys and the sine wave object
		// as value
		for (int i = 0; i < TTS_PHONEMES.length; i++) {
			// System.out.println("note to add: " + midiFreq(i+midiNoteOffset));
			SineWave sine = new SineWave(midiFreq(i + midiNoteOffset),
					(float) .3, 44100f);

			// SineWave sine = new SineWave((float) (freqStep * i), (float) 0.5,
			// 44100f);
			phonemeSine.put(TTS_PHONEMES[i], sine);
		}
	}

	public void setWords() {
//		System.out.println("setWords(): start");

		// clear old set of words & phonemes first
		words.clear();

		// break up sentences
		inputText = inputText.replaceAll("[^a-z^A-Z_ ^0-9]*", "");
		analyze.analyze(inputText);

		String[] twords = analyze.getTokens();
		for (int i = 0; i < twords.length; i++) {

			if (twords[i].length() == 0)
				continue;

			analyze.analyze(twords[i]);

//			System.out.println("Word: " + twords[i]);

			String[] tempPhonemes = analyze.getPhonemes().split(":");
			Vector<String> phonemes = new Vector<String>();

			if (debug)
				System.out.print("(");

			for (int j = 0; j < tempPhonemes.length; j++) {
				String ph = tempPhonemes[j];

				if (ph.length() > 1)
					ph = ph.toUpperCase();

				phonemes.add(ph);

				if (debug)
					System.out.print(ph + ":");
			}
			words.put(twords[i], phonemes);
			if (debug)
				System.out.print(")\n");
		}

//		System.out.println("setWords(): done!");
	}

	public void generateSounds() {
//		System.out.println("generateSounds(): start playing");

		Iterator<String> it = words.keySet().iterator();
		while (it.hasNext()) {
			Object key = it.next();
			Vector<String> value = (Vector<String>) words.get(key);
//			 System.out.println("key: " + key + ", value: " + value);

			// duration in millis
			int duration = key.toString().length() * speed;

			currentWord = (String) key;
			switch (mode) {
			case 0:
				seqMode(value, duration);
				break;
			case 1:
				simMode(value, duration);
				break;
			case 2:
				smoothMode(value, duration);
				break;
			case 3:
				bothMode(value, duration);
				break;
			default:
				break;
			}
		}

//		System.out.println("generateSounds(): done");
	}

	private void seqMode(Vector<String> phon, int duration) {
		for (int i = 0; i < phon.size(); i++) {
			out.addSignal((SineWave) phonemeSine.get(phon.get(i)));
			// wait for single note to play before turning it off.
			try {
				Thread.sleep(speed);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			out.removeSignal((SineWave) phonemeSine.get(phon.get(i)));
		}
		// pause between words
		try {
			Thread.sleep(pause);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void smoothMode(Vector<String> phon, int duration) {
		SineWave sine = (SineWave) phonemeSine.get(phon.get(0));
		out.addSignal(sine);
		for (int i = 1; i < phon.size(); i++) {

			p.drawPhoneme(phon.get(i));

			Float f = (Float) phonemeFreq.get(phon.get(i));
			float f1 = f.floatValue();
			sine.setFreq(f1);
			try {
				Thread.sleep(duration);
			} catch (InterruptedException e) {
			}
		}

		out.removeSignal(sine);

		try {
			Thread.sleep(pause);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void bothMode(Vector<String> phon, int duration) {

		for (int i = 0; i < phon.size(); i++) {
			out.addSignal((SineWave) phonemeSine.get(phon.get(i)));
		}
		// wait for notes to play before turning them off
		SineWave sine = (SineWave) phonemeSine.get(phon.get(0));
		out.addSignal(sine);
		for (int i = 1; i < phon.size(); i++) {
			Float f = (Float) phonemeFreq.get(phon.get(i));
			float f1 = f.floatValue();
			sine.setFreq(f1);
			try {
				Thread.sleep(duration);
			} catch (InterruptedException e) {
			}
		}

		out.removeSignal(sine);
		for (int i = 0; i < phon.size(); i++) {
			out.removeSignal((SineWave) phonemeSine.get(phon.get(i)));
		}
		// pause between words
		try {
			Thread.sleep(pause);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void simMode(Vector<String> phon, int duration) {
		for (int i = 0; i < phon.size(); i++) {
			out.addSignal((SineWave) phonemeSine.get(phon.get(i)));
		}
		// wait for notes to play before turning them off
		try {
			Thread.sleep(duration);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		for (int i = 0; i < phon.size(); i++) {
			out.removeSignal((SineWave) phonemeSine.get(phon.get(i)));
		}
		// pause between words
		try {
			Thread.sleep(pause);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

	}

	public float midiFreq(int midiNote) {
		float freq = 0;
		int a = 440; // a is 440 hz...

		freq = (float) ((a / 32f) * Math.pow(2, ((midiNote - 9f) / 12f)));
		return freq;
	}

	public void setPause(int i) {
		pause = i;
	}

	public void setMode(int m) {
		// 0 is sequential, 1 is simultaneous, 2 is smooth
		// sequential,3 is both
		System.out.println("Setting mode:" + m);
		mode = m;
	}

	public void prepChorus(SparqlQuery sq) {
		System.out.println("prepChorus() starting analysis for: "
				+ sq.getLabel());
		sqStr = sq.getLabel();
		
		myThread.interrupt();
		isBusy = true;
	}

	public void stop() {
		// always close Minim audio classes
		out.close();
	}

	public boolean isBusy() {
		return isBusy;
	}

	public synchronized void run() {
		while (true) {

			if (isBusy) {
				System.out.println("prepchord start!");

				setSpeed(20);
				setMidiNoteOffset(40);
				setMode(2);
				setPause(2);
				setText(sqStr);
				setWords();
				loadFreq();

				p.setFadeFlag(false);
				p.background(0);
				p.drawLabel(sqStr);

				System.out.println("prepchord done!");
				
				// wait a little to prevent stutter
				try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				generateSounds();
				
				p.v.prep(sqStr);
				p.v.speak(words);

				isBusy = false;
			}
		}
	}

	public Map<String, Vector<String>> getWords() {
		return words;
	}
}
